home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 015 / prndsk.arc / PRNDSK.ASM next >
Assembly Source File  |  1987-03-03  |  41KB  |  1,789 lines

  1.     page    60,132
  2.  
  3. ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  4. ; *                                    *
  5. ; *    PRNDSK.ASM  Version 1.00 (B)                    *
  6. ; *                                    *
  7. ; *    A utility to redirect output to a printer or comm port to    *
  8. ; *    an MS-DOS disk file.                        *
  9. ; *                                    *
  10. ; *    Copyright 1987 by David H. Rifkind                *
  11. ; *                                    *
  12. ; *    Version 1.00  2 Mar 87                        *
  13. ; *                                    *
  14. ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  15.  
  16.  
  17. MAXSPEC        equ    80        ; Maximum length of a filespec
  18.  
  19.  
  20. ;
  21. ;    Buffer size and flush point definitions
  22. ;
  23.  
  24. buffer_size    equ    800h        ; Size of each device buffer
  25. buffer_mark    equ    400h        ; Flush point for all buffers
  26.  
  27.  
  28. ;
  29. ;    Beginning of program segment
  30. ;
  31.  
  32. Code    SEGMENT    para
  33.     assume    cs:Code,ds:Code
  34.  
  35. ;    PSP structure definitions
  36.  
  37.         org    2Ch
  38. env_seg_ptr    label    word        ; Environment segment pointer
  39.         org    81h
  40. command_line    label    byte        ; Program command line tail
  41.  
  42.  
  43. ;
  44. ;    Beginning of COM binary image
  45. ;
  46.     org    100h
  47. Start:    jmp    PrnDsk            ; COM entry point vector
  48.  
  49. program_id    db    "PRNDSK 1.00",0    ; Program identification
  50. id_length    equ    $-program_id
  51.  
  52.  
  53. ;
  54. ;    Resident data area
  55. ;
  56.  
  57. tsr_pid        dw    ?        ; Program ID of TSR part of PRNDSK
  58. dos_version    db    ?        ; DOS major version number
  59. reentry_flag    db    0        ; Non-zero indicates our stack in use
  60. under_0Ch_flag    db    0        ; DOS service 1 <= AH <= 0Ch in use
  61. over_0Ch_flag    db    0        ; DOS service 0 or > 0Ch in use
  62. defer_flag    db    0        ; Non-zero indicates buffer(s) waiting
  63. crit_flag    db    ?        ; Non-zero indicates critical error
  64. ctxt_caller    dw    ?        ; InContext/OutContext return address
  65.  
  66. user_pid    dw    ?        ; User's (calling program's) PID
  67. user_ax        dw    ?        ; User's AX (temporary storage)
  68. user_sp        dw    ?        ; User's SP
  69. user_ss        dw    ?        ;   and SS
  70. user_cc        db    ?        ; User's break (control-C) check flag
  71.  
  72. old_int_14h    dw    ?,?        ; Original vectors for INT 14h,
  73. old_int_17h    dw    ?,?        ;   INT 17h,
  74. old_int_21h    dw    ?,?        ;   and INT 21h
  75. old_int_24h    dw    ?,?        ; User's critical error vector
  76.  
  77.  
  78. ;
  79. ;    Printer Redirection Block structure definition
  80. ;
  81.  
  82. printer_struc    STRUC
  83.  
  84. prb_buf_ptr    dw    ?        ; Pointer to start of buffer area
  85. prb_buf_size    dw    ?        ; Size of buffer
  86. prb_buf_mark    dw    ?        ; Buffer flush point
  87. prb_buf_count    dw    ?        ; Number of bytes currently in buffer
  88.  
  89. prb_sys_flags    db    0        ; System flag byte (see below)
  90. prb_user_flags    db    0        ; User flag byte (see below)
  91. prb_handle    dw    ?        ; File handle for this device
  92. prb_status    db    ?        ; Last DOS file status
  93.  
  94. prb_filespec    db    MAXSPEC dup (?)    ; Redirection file specification
  95.  
  96. printer_struc    ENDS
  97.  
  98. ;    Definitions of bits in prb_sys_flags (prbs_xxxx) and
  99. ;    prb_user_flags (prbu_xxxx)
  100.  
  101. prbs_active    equ    80h        ; 1 indicates device being redirected
  102. prbs_open    equ    40h        ; 1 indicates redirection file is open
  103.  
  104. prbu_append    equ    80h        ; 1 indicates file to be appended
  105. prbu_transp    equ    40h        ; 1 indicates transparent redirection
  106.  
  107. ;    Printer Redirection Blocks - one for each redirectable device
  108.  
  109. lpt1_prb    printer_struc    <lpt1_buf,buffer_size,buffer_mark>
  110. lpt2_prb    printer_struc    <lpt2_buf,buffer_size,buffer_mark>
  111. lpt3_prb    printer_struc    <lpt3_buf,buffer_size,buffer_mark>
  112. com1_prb    printer_struc    <com1_buf,buffer_size,buffer_mark>
  113. com2_prb    printer_struc    <com2_buf,buffer_size,buffer_mark>
  114.  
  115. ;    Table of pointers to PRBs
  116.  
  117. printer_prbs    label    word        ; Pointers to printer PRBs
  118.         dw    offset lpt1_prb
  119.         dw    offset lpt2_prb
  120.         dw    offset lpt3_prb
  121. comm_prbs    label    word        ; Pointers to comm port PRBs
  122.         dw    offset com1_prb
  123.         dw    offset com2_prb
  124.  
  125.     page
  126.  
  127. ;
  128. ;    InContext switches to the TSR's stack area and data segment.
  129. ;    User registers are saved at fixed locations on the stack (stack_ax,
  130. ;    stack_es, etc.) and can be directly referenced.
  131. ;
  132.  
  133. InContext PROC    near
  134.  
  135.     mov    cs:reentry_flag,0FFh    ; Set reentry flag
  136.     pop    cs:ctxt_caller        ; Save return address
  137.  
  138.     mov    cs:user_ax,ax        ; (Temporary)
  139.     mov    cs:user_sp,sp        ; Save user's stack pointer
  140.     mov    cs:user_ss,ss        ;   and segment
  141.     mov    ax,cs
  142.     cli
  143.     mov    ss,ax            ; Use our stack pointer
  144.     mov    sp,offset stack_top
  145.     sti
  146.  
  147.     mov    ax,cs:user_ax        ; Reload AX...
  148.     push    ax            ; ...and store it again
  149.     push    bx            ; Save all registers
  150.     push    cx
  151.     push    dx
  152.     push    bp
  153.     push    si
  154.     push    di
  155.     push    ds
  156.     push    es
  157.  
  158.     push    cs            ; Move CS...
  159.     pop    ds            ; ...to DS
  160.  
  161.     jmp    ctxt_caller        ; Return to caller
  162.  
  163. InContext ENDP
  164.  
  165.  
  166. ;
  167. ;    Switch back to the user's stack and data segment.  Registers
  168. ;    are first restored from our stack; some may have been modified.
  169. ;
  170.  
  171. OutContext PROC    near
  172.  
  173.     pop    ctxt_caller        ; Save return address
  174.  
  175.     pop    es            ; Restore user's registers
  176.     pop    ds
  177.     pop    di
  178.     pop    si
  179.     pop    bp
  180.     pop    dx
  181.     pop    cx
  182.     pop    bx
  183.     pop    ax
  184.  
  185.     cli
  186.     mov    ss,cs:user_ss        ; Restore user's stack pointer
  187.     mov    sp,cs:user_sp
  188.     sti
  189.  
  190.     push    cs:ctxt_caller        ; Push return address
  191.     mov    cs:reentry_flag,0h    ; Clear reentrancy flag
  192.     ret
  193.  
  194. OutContext ENDP
  195.  
  196.  
  197. ;
  198. ;    Prepare for DOS calls from within the TSR.  Sets the break check
  199. ;    flag, critical error vector and current PID.
  200. ;
  201.  
  202. InTSR    PROC    near
  203.  
  204.     mov    ax,3300h        ; DOS service:
  205.     int    21h            ;   Get break flag
  206.     mov    user_cc,dl        ; Save user's break flag
  207.  
  208.     mov    dl,0h
  209.     mov    ax,3301h        ; DOS service:
  210.     int    21h            ;   Set break flag (0 = no break check)
  211.  
  212.     mov    ax,3524h        ; DOS service:
  213.     int    21h            ;   Get INT 24h vector
  214.     mov    old_int_24h+0,bx    ; Save user's critical error vector
  215.     mov    old_int_24h+2,es
  216.  
  217.     mov    dx,offset Int_24h    ; Point to our critical error handler
  218.     mov    ax,2524h        ; DOS service:
  219.     int    21h            ;   Set INT 24h vector
  220.     mov    crit_flag,0h        ; Clear critical error flag
  221.  
  222.     mov    ah,51h            ; DOS service:
  223.     int    21h            ;   Get Process ID
  224.     mov    user_pid,bx        ; Save user's PID
  225.  
  226.     mov    bx,tsr_pid        ; Load our Process ID
  227.     mov    ah,50h            ; DOS service:
  228.     int    21h            ;   Set PID
  229.  
  230.     ret
  231.  
  232. InTSR    ENDP
  233.  
  234.  
  235. ;
  236. ;    Restore DOS's state for the user's program.  Flags and vectors
  237. ;    changed by InTSR are restored.
  238. ;
  239.  
  240. OutTSR    PROC    near
  241.  
  242.     mov    bx,user_pid        ; Load user's PID
  243.     mov    ah,50h            ; DOS service:
  244.     int    21h            ;   Set Process ID
  245.  
  246.  
  247.     mov    dx,old_int_24h+0    ; Point to user's critical error
  248.     mov    ax,old_int_24h+2    ;   handler
  249.     push    ds
  250.     mov    ds,ax
  251.     mov    ax,2524h        ; DOS service:
  252.     int    21h            ;   Set INT 24h vector
  253.     pop    ds
  254.  
  255.     mov    dl,user_cc        ; Get user's break flag
  256.     mov    ax,3301h        ; DOS service:
  257.     int    21h            ;   Set break flag
  258.  
  259.     ret
  260.  
  261. OutTSR    ENDP
  262.  
  263.  
  264. ;
  265. ;    Critical error (INT 24h) interrupt handler
  266. ;
  267. ;    Called when a critical error occurs while performing DOS
  268. ;    commands from within the TSR, to prevent unwanted "Retry,
  269. ;    Ignore, Abort?" messages.
  270. ;
  271.  
  272. Int_24h    PROC    far
  273.  
  274.     mov    cs:crit_flag,0FFh    ; Set critical error flag
  275.  
  276.     mov    al,0            ; INT 24h "ignore" flag
  277.     cmp    cs:dos_version,3    ; DOS 3.xx or better?
  278.     jb    int_24h_exit
  279.     mov    al,3            ; Yes - return "fail" flag
  280. int_24h_exit:
  281.     iret
  282.  
  283. Int_24h    ENDP
  284.  
  285.     page
  286.  
  287. ;
  288. ;    Flush a redirection buffer.  On entry, SI points to the PRB
  289. ;    for an active device.  If the associated file is not yet open,
  290. ;    it is opened (this can happen only in one special case).
  291. ;    InTSR should be called BEFORE calling Flush.
  292. ;
  293.  
  294. Flush    PROC    near
  295.  
  296.     test    prb_sys_flags[si],prbs_open
  297.                     ; Is the file open?
  298.     jnz    flush_write        ; Yes - go write to file
  299.  
  300.     call    FileOpen        ; No - open the file
  301.     test    prb_status[si],0FFh    ; Check for error
  302.     jnz    flush_exit
  303.  
  304. flush_write:
  305.     mov    bx,prb_handle[si]    ; BX = file handle
  306.     mov    cx,prb_buf_count[si]    ; CX = number of bytes to write
  307.     mov    dx,prb_buf_ptr[si]    ; DX = address of buffer
  308.     mov    ah,40h            ; DOS service:
  309.     int    21h            ;   Write to file
  310.     jc    flush_error
  311.     test    crit_flag,0FFh        ; Check for critical error
  312.     jnz    flush_crit
  313.  
  314.     mov    prb_buf_count[si],0    ; Mark buffer as empty
  315.  
  316.     jmp    flush_exit
  317.  
  318. flush_error:
  319.     test    crit_flag,0FFh
  320.     jz    flush_err_1
  321. flush_crit:
  322.     mov    al,83            ; Fake DOS 3.xx critical error code
  323. flush_err_1:
  324.     mov    prb_status[si],al    ; Set file error status byte
  325. flush_exit:
  326.     ret
  327.  
  328. Flush    ENDP
  329.  
  330.  
  331. ;
  332. ;    Open a redirection file.  On entry, SI points to the PRB
  333. ;    for a device.
  334. ;
  335.  
  336. FileOpen PROC    near
  337.  
  338.     test    prb_user_flags[si],prbu_append
  339.                     ; File to be appended to?
  340.     jz    file_create        ; No - go truncate file
  341.  
  342.     lea    dx,prb_filespec[si]    ; DX points to filespec
  343.     mov    ax,3D01h        ; DOS service:
  344.     int    21h            ;   Open file (access = write)
  345.     jc    open_1
  346.     test    crit_flag,0FFh        ; Check for critical error
  347.     jnz    open_crit
  348.     jmp    open_done
  349. open_1:                    ; Can't open file
  350.     cmp    al,2            ; Check for "file not found"
  351.     jnz    open_error        ; No - it's a real error
  352.  
  353. file_create:
  354.     lea    dx,prb_filespec[si]    ; DX points to filespec
  355.     mov    cx,0            ; CX = attribute (0 = normal file)
  356.     mov    ah,3Ch            ; DOS service:
  357.     int    21h            ;   Create or truncate file
  358.     jc    open_error
  359.     test    crit_flag,0FFh        ; Check for critical error
  360.     jnz    open_crit
  361.  
  362. open_done:
  363.     mov    prb_handle[si],ax    ; Save file handle
  364.     or    prb_sys_flags[si],prbs_open
  365.                     ; Flag file as open
  366.  
  367.     mov    ax,user_pid        ; If the user's PID matches ours,
  368.     cmp    ax,tsr_pid        ;   the file will be closed on exit
  369.     jnz    open_2
  370.     and    prb_sys_flags[si],not prbs_open
  371.                     ; Mark file as closed (!!!)
  372.  
  373. open_2:
  374.     mov    bx,prb_handle[si]    ; BX = file handle
  375.     xor    cx,cx            ; Set offset (CX and DX) to zero
  376.     xor    dx,dx
  377.     mov    ax,4202h        ; DOS service:
  378.     int    21h            ;   Move file pointer (to EOF)
  379.     jc    open_error
  380.     test    crit_flag,0FFh        ; Check for critical error
  381.     jz    open_exit
  382.     jmp    open_crit
  383.  
  384. open_error:
  385.     test    crit_flag,0FFh
  386.     jz    open_err_1
  387. open_crit:
  388.     mov    al,83            ; Fake DOS 3.xx critical error code
  389. open_err_1:
  390.     mov    prb_status[si],al    ; Set file status byte
  391. open_exit:
  392.     ret
  393.  
  394. FileOpen ENDP
  395.  
  396.  
  397. ;
  398. ;    Check the current redirection buffer and see if it needs
  399. ;    to (and can) be flushed.  Calls InTSR and OutTSR to set up
  400. ;    the environment for the Flush call.
  401. ;
  402. ;    Entry:    SI points to PRB
  403. ;
  404.  
  405. CheckBuf PROC    near
  406.  
  407.     test    prb_sys_flags[si],prbs_active
  408.                     ; Redirection active for this device?
  409.     jz    check_exit        ; No - skip it
  410.     test    prb_status[si],0FFh    ; Redirection stopped by error?
  411.     jnz    check_exit        ; Yes - skip it
  412.  
  413.     mov    ax,prb_buf_count[si]    ; Check whether buffer is over the
  414.     cmp    ax,prb_buf_mark[si]    ;   trigger point
  415.     jb    check_exit        ; No - don't flush now
  416.  
  417.     test    over_0Ch_flag,0FFh    ; Test whether DOS is idle
  418.     jnz    cant_flush        ; Not idle - can't flush
  419.     cmp    dos_version,3        ; DOS version 3 or better...
  420.     jae    check_buf_1        ; ...doesn't care about 1 - 12
  421.     test    under_0Ch_flag,0FFh
  422.     jnz    cant_flush        ; Not idle - can't flush
  423.  
  424. check_buf_1:
  425.     call    InTSR            ; Set up for DOS access
  426.     call    Flush            ; Flush the buffer
  427.     call    OutTSR            ; Put DOS back the way you found it
  428.  
  429. check_exit:
  430.     ret
  431.  
  432. cant_flush:
  433.     mov    defer_flag,0FFh        ; Request for service when DOS
  434.                     ;   becomes free
  435.     ret
  436.  
  437. CheckBuf ENDP
  438.  
  439.  
  440. ;
  441. ;    Add a character to a redirection buffer and call CheckBuf
  442. ;    to try to flush it.
  443. ;
  444. ;    Entry:    AL = character
  445. ;        SI points to PRB
  446. ;
  447.  
  448. BufChar    PROC    near
  449.  
  450.     mov    di,prb_buf_count[si]    ; Get buffer index
  451.     cmp    di,prb_buf_size[si]    ; Check for space in buffer
  452.     jnb    buf_char_1        ; Full - discard this character
  453.  
  454.     mov    bx,prb_buf_ptr[si]    ; Pointer to start of buffer
  455.     mov    [bx+di],al        ; Store character in buffer
  456.     inc    di            ; Increment buffer count
  457.     mov    prb_buf_count[si],di    ;   and store it
  458.  
  459. buf_char_1:
  460.     call    CheckBuf        ; Now try to flush the buffer
  461.  
  462.     ret
  463.  
  464. BufChar    ENDP
  465.  
  466.     page
  467.  
  468. ;
  469. ;    DOS service (INT 21h) interrupt handler
  470. ;
  471. ;    This routine replaces the MS-DOS INT 21h service request
  472. ;    handler.  It keeps track of entries and exits to make sure
  473. ;    that DOS will not be called reentrantly when flushing a
  474. ;    redirection buffer.
  475. ;
  476.  
  477. Int_21h    PROC    far
  478.     sti
  479.  
  480.     cmp    ah,0h            ; Service 0h uses second stack
  481.     jz    over_0Ch
  482.  
  483.     cmp    ah,4Bh            ; EXEC requires special treatment
  484.     jz    service_4Bh
  485.  
  486.     cmp    ah,50h            ; Services 50h and 51h use first
  487.     jz    under_0Ch        ;   stack (DOS 2.xx only, but not
  488.     cmp    ah,51h            ;   worth the trouble to handle
  489.     jz    under_0Ch        ;   as a special case)
  490.  
  491.     cmp    ah,0Ch            ; All other services over 0Ch use
  492.     ja    over_0Ch        ;   second stack
  493.  
  494. under_0Ch:
  495.     mov    cs:under_0Ch_flag,0FFh    ; Flag DOS as busy
  496.     pushf
  497.     call    dword ptr cs:old_int_21h
  498.                     ; Call the old INT 21h handler
  499.     mov    cs:under_0Ch_flag,0h    ; Clear "DOS busy" flag
  500.     jmp    int_21h_1
  501.  
  502. over_0Ch:
  503.     mov    cs:over_0Ch_flag,0FFh    ; Flag DOS as busy
  504.     pushf
  505.     call    dword ptr cs:old_int_21h
  506.                     ; Call the old INT 21h handler
  507.     mov    cs:over_0Ch_flag,0h    ; Clear the "DOS busy" flag
  508.     jmp    int_21h_1
  509.  
  510. service_4Bh:
  511.     mov    cs:over_0Ch_flag,0FFh    ; Flag DOS busy
  512.     jmp    dword ptr cs:old_int_21h
  513.                     ; Jump directly to old INT 21h
  514.  
  515.  
  516. ;    Now see whether any redirection buffers need flushing.
  517.  
  518. int_21h_1:
  519.     pushf
  520.     test    cs:reentry_flag,0FFh    ; Check for recursive call
  521.     jnz    int_21h_exit
  522.     test    cs:defer_flag,0FFh    ; Check for deferred service request
  523.     jz    int_21h_exit
  524.  
  525.     test    cs:under_0Ch_flag,0FFh    ; Check whether DOS is really idle
  526.     jnz    int_21h_exit
  527.     test    cs:over_0Ch_flag,0FFh
  528.     jnz    int_21h_exit
  529.  
  530.     call    InContext        ; Use local stack and registers
  531.  
  532.     mov    bx,0            ; Start with buffer for LPT1
  533. int_21h_loop:
  534.     push    bx
  535.     shl    bx,1            ; Offset into word table
  536.     mov    si,printer_prbs[bx]    ; Get address of PRB
  537.     call    CheckBuf        ; Consider flushing this buffer
  538.     pop    bx
  539.     inc    bx            ; Next PRB...
  540.     cmp    bx,5            ; (Highest PRB number is 4)
  541.     jb    int_21h_loop
  542.  
  543.     mov    defer_flag,0h        ; Clear deferred service flag
  544.  
  545.     call    OutContext        ; Restore user's stack and registers
  546.  
  547. int_21h_exit:
  548.     popf
  549.     ret    2            ; Return flags to caller
  550.  
  551. Int_21h    ENDP
  552.  
  553.     page
  554.  
  555. ;
  556. ;    Printer service (INT 17h) interrupt handler
  557. ;
  558. ;    This replaces the original INT 17h handler (and chains to
  559. ;    it as needed).  In addition to redefining the standard printer
  560. ;    services, it adds seven new ones (function codes 80h to 86h)
  561. ;    to control redirection.
  562. ;
  563. ;    Original services
  564. ;    -------- --------
  565. ;    DX = printer number (0 to 2)
  566. ;
  567. ;    Entry:    AH = 0 to print a character
  568. ;            AL = character to be printed
  569. ;        AH = 1 to initialize printer
  570. ;        AH = 2 to return printer status
  571. ;
  572. ;    Exit:    AH = printer status:
  573. ;            Bit 0 = printer timeout
  574. ;            Bit 3 = I/O error
  575. ;            Bit 4 = device online
  576. ;            Bit 5 = out of paper
  577. ;            Bit 6 = acknowledge
  578. ;            Bit 7 = printer ready
  579. ;
  580. ;    New Services
  581. ;    --- --------
  582. ;    DX = device number (0 to 4) for services 81h to 85h
  583. ;
  584. ;    Entry:    AH = 80h to return installed state flag
  585. ;    Exit:    AX = 5A5Ah
  586. ;        ES points to TSR code segment
  587. ;
  588. ;    Entry:    AH = 81h to start redirecting a device
  589. ;            AL = option flags (prb_user_flags)
  590. ;            DS:SI points to filespec
  591. ;        AH = 82h to terminate redirection and close file
  592. ;        AH = 83h to flush buffer to disk and update file
  593. ;    Exit:    AL = DOS error code (zero if no error) (1 byte only!)
  594. ;
  595. ;    Entry:    AH = 84h to return pointer to redirection block
  596. ;    Exit:    ES:BX points to printer redirection block
  597. ;
  598. ;    Entry:    AH = 85h to change the option flags
  599. ;            AL = new option flags
  600. ;
  601. ;    Entry:    AH = 86h to return the option flags
  602. ;    Exit:    AL = option flags
  603. ;    
  604.  
  605. Int_17h    PROC    far
  606.  
  607.     test    cs:reentry_flag,0FFh    ; Reentrant call (unlikely case)
  608.     jnz    jmp_old_17h        ; Yes - can't do anything safely
  609.     call    InContext        ; Switch in our stack and segments
  610.     sti                ; Allow hardware interrupts
  611.  
  612.     cmp    ah,80h            ; New service?  (80h <= AH <= 85h)
  613.     jae    new_service
  614.  
  615.     cmp    dx,2            ; Check printer number 0 to 2
  616.     ja    use_old_17h        ; Greater than 2 - can't deal with it
  617.     mov    bx,dx
  618.     shl    bx,1            ; Times two for word offset
  619.     mov    si,printer_prbs[bx]    ; SI points to PRB
  620.  
  621.     test    prb_sys_flags[si],prbs_active
  622.                     ; Device being redirected?
  623.     jz    use_old_17h        ; No - skip it
  624.  
  625.     cmp    ah,0
  626.     jz    prt_char        ; Service 0
  627.     cmp    ah,1
  628.     jz    prt_init        ; Service 1
  629.     cmp    ah,2
  630.     jz    prt_stat        ; Service 2
  631.  
  632.     jmp    use_old_17h        ; Unknown service number
  633.  
  634. new_service:
  635.     cmp    ah,80h            ; Installation check?
  636.     jz    install_flag        ; Yes - don't check DX
  637.  
  638.     cmp    dx,4            ; Device number 0 to 4 (3 & 4 are COM)?
  639.     ja    use_old_17h        ; No - can't handle it
  640.     mov    bx,dx
  641.     shl    bx,1
  642.     mov    si,printer_prbs[bx]    ; SI points to redirection block
  643.  
  644.     cmp    ah,81h
  645.     jz    set_redir        ; Service 81h
  646.     cmp    ah,82h
  647.     jz    clear_redir        ; Service 82h
  648.     cmp    ah,83h
  649.     jz    flush_redir        ; Service 83h
  650.     cmp    ah,84h
  651.     jz    redir_stat        ; Service 84h
  652.     cmp    ah,85h
  653.     jz    redir_flags        ; Service 85h
  654.     cmp    ah,86h
  655.     jz    return_flags        ; Service 86h
  656.  
  657. use_old_17h:
  658.     mov    ax,stack_ax        ; Registers changed by our handler
  659.     mov    dx,stack_dx
  660.     pushf                ; Simulate an INT...
  661.     call    dword ptr old_int_17h    ; ...to the old INT 17h handler
  662.     mov    stack_ax,ax        ; Store return value
  663.  
  664. int_17h_done:
  665.     call    OutContext        ; Restore user registers and segments
  666.  
  667. int_17h_exit:
  668.     iret
  669.  
  670. jmp_old_17h:
  671.     jmp    cs:dword ptr old_int_17h
  672.  
  673.  
  674. ;    Routines for individual INT 17h services
  675.  
  676. prt_char:                ; Print a character:
  677.     test    prb_status[si],0FFh    ; Stopped on DOS error?
  678.     jnz    prt_error        ; Yes - don't buffer anything
  679.     call    BufChar            ; No - put character in buffer
  680.  
  681. prt_init:                ; Initialize printer (no operation)
  682. prt_stat:                ; Return printer status
  683.     test    prb_status[si],0FFh    ; Stopped on DOS error?
  684.     jnz    prt_error        ; Yes - return printer error
  685.     test    prb_user_flags[si],prbu_transp
  686.                     ; Transparent redirection?
  687.     jnz    use_old_17h        ; Yes - chain to old handler
  688.  
  689.     mov    stack_ah,90h        ; Printer OK return code
  690.     jmp    int_17h_done
  691.  
  692. prt_error:
  693.     mov    stack_ah,08h        ; Printer I/O error code
  694.     jmp    int_17h_done
  695.  
  696.  
  697. install_flag:                ; Return installed state flag
  698.     mov    stack_es,ds
  699.     mov    stack_ax,5A5Ah
  700.     jmp    int_17h_done
  701.  
  702. set_redir:                ; Redirect device
  703.     call    InTSR
  704.     call    SetRedir
  705.     call    OutTSR
  706. return_status:                ; Use channel status for return code
  707.     mov    al,prb_status[si]
  708.     mov    stack_al,al
  709.     jmp    int_17h_done
  710.  
  711. clear_redir:                ; Terminate redirection
  712.     call    InTSR
  713.     call    ClearRedir
  714.     call    OutTSR
  715.     jmp    return_status
  716.  
  717. flush_redir:                ; Flush file to disk
  718.     call    InTSR
  719.     call    FlushRedir
  720.     call    OutTSR
  721.     jmp    return_status
  722.  
  723. redir_stat:                ; Return PRB pointer
  724.     mov    stack_bx,si
  725.     mov    stack_es,ds
  726.     jmp    int_17h_done
  727.  
  728. redir_flags:                ; Set user flag byte
  729.     mov    prb_user_flags[si],al
  730.     jmp    int_17h_done
  731.  
  732. return_flags:                ; Return user flag byte
  733.     mov    al,prb_user_flags[si]
  734.     mov    stack_al,al
  735.     jmp    int_17h_done
  736.  
  737. Int_17h    ENDP
  738.  
  739.     page
  740.  
  741. ;
  742. ;    Serial device service (INT 14h) interrupt handler
  743. ;
  744. ;    This handler replaces the original INT 14h handler.  Like
  745. ;    the INT 17h handler, it handles redirection to files, but
  746. ;    adds no new services (the INT 17h services work for both
  747. ;    printer and comm port redirection).
  748. ;
  749. ;    Entry:    DX = comm port number (0 or 1)
  750. ;        AH = 0 to initialize comm port
  751. ;            AL = initialization parameters (see Tech. Ref.)
  752. ;        AH = 1 to transmit a character
  753. ;            AL = character to send
  754. ;        AH = 2 to receive a character
  755. ;        AH = 3 to return serial port status
  756. ;
  757. ;    Exit:    AH = comm port status:
  758. ;            Bit 0 = data ready
  759. ;            Bit 1 = overrun error
  760. ;            Bit 2 = parity error
  761. ;            Bit 3 = framing error
  762. ;            Bit 4 = break detect
  763. ;            Bit 5 = xmit holding register empty
  764. ;            Bit 6 = xmit shift register empty
  765. ;            Bit 7 = time out or general error
  766. ;
  767. ;        AL = received character for service 2
  768. ;        AL = line status for service 3 (see Tech. Ref.)
  769. ;
  770.  
  771. Int_14h    PROC    near
  772.  
  773.     test    cs:reentry_flag,0FFh    ; Check for reentry
  774.     jnz    jmp_old_14h        ; Reentry - can't do anything
  775.     call    InContext        ; Use our stack and registers
  776.     sti                ; Allow hardware interrupts
  777.  
  778.     cmp    dx,1            ; Comm port number 0 or 1?
  779.     ja    use_old_14h        ; No - can't handle it
  780.     mov    bx,dx
  781.     shl    bx,1
  782.     mov    si,comm_prbs[bx]    ; SI points to PRB
  783.  
  784.     test    prb_sys_flags[si],prbs_active
  785.                     ; Redirection active for this port?
  786.     jz    use_old_14h        ; No - let the old handler have it
  787.  
  788.     cmp    ah,0
  789.     jz    comm_init        ; Service 0
  790.     cmp    ah,1
  791.     jz    comm_out        ; Service 1
  792.     cmp    ah,2
  793.     jz    comm_in            ; Service 2
  794.     cmp    ah,3
  795.     jz    comm_stat        ; Service 3
  796.  
  797. use_old_14h:
  798.     mov    ax,stack_ax        ; Registers changed by handler
  799.     mov    dx,stack_dx
  800.     pushf                ; Simulate and interrupt
  801.     call    dword ptr old_int_14h    ;   to the old handler
  802.     mov    stack_ax,ax        ; Save return code
  803.  
  804. int_14h_done:
  805.     call    OutContext        ; Restore user registers and stack
  806.  
  807. int_14h_exit:
  808.     iret
  809.  
  810. jmp_old_14h:
  811.     jmp    dword ptr cs:old_int_14h
  812.  
  813.  
  814. ;    Routines to handle individual INT 14h services
  815.  
  816. comm_out:                ; Output a character
  817.     test    prb_status[si],0FFh    ; Stopped on DOS error?
  818.     jnz    comm_error
  819.     call    BufChar            ; No - buffer the character
  820.  
  821. comm_init:                ; Initialize comm port (no op)
  822. comm_stat:                ; Return comm port status
  823.     test    prb_status[si],0FFh    ; Stopped on error?
  824.     jnz    comm_error
  825.     test    prb_user_flags[si],prbu_transp
  826.                     ; Transparent redirection?
  827.     jnz    use_old_14h        ; Yes - chain to old handler
  828.     mov    stack_ax,6030h        ; Return "all OK" code
  829.     jmp    int_14h_done
  830.  
  831. comm_in:                ; Input from comm port
  832.     test    prb_user_flags[si],prbu_transp
  833.                     ; Transparent redirection...
  834.     jnz    use_old_14h        ; ...uses old handler...
  835.                     ; ...else, return an error
  836.  
  837. comm_error:
  838.     mov    stack_ax,8000h        ; General-purpose serial error code
  839.     jmp    int_14h_done
  840.  
  841. Int_14h    ENDP
  842.  
  843.     page
  844.  
  845. ;
  846. ;    Set device redirection.  Close any file already associated with
  847. ;    this device, then copy the new filespec and user flags and open
  848. ;    the file.  If this is the first call to SetRedir (i.e., PRNDSK
  849. ;    is just in the process of installing itself), the file will end
  850. ;    up open but flagged as closed, and will actually be closed when
  851. ;    the program exits (see FileOpen for details).
  852. ;
  853.  
  854. SetRedir PROC    near
  855.  
  856.     call    ClearRedir        ; Close old file (ignore return code)
  857.  
  858.     mov    al,stack_al        ; Copy user flags (transparent and
  859.     mov    prb_user_flags[si],al    ;   append mode bits)
  860.  
  861.     mov    es,stack_ds
  862.     mov    bx,stack_si        ; ES:BX points to filespec
  863.     lea    di,prb_filespec[si]    ; DS:DI is destination for filespec
  864.     mov    cx,MAXSPEC-1        ; Maximum non-zero characters to copy
  865. copy_spec:
  866.     mov    al,byte ptr es:[bx]    ; Move one character
  867.     mov    byte ptr [di],al
  868.     inc    bx            ; Advance character pointers
  869.     inc    di
  870.     test    al,al            ; Check for end of name
  871.     loopnz    copy_spec        ; Repeat until end
  872.     mov    byte ptr [di],0        ; Just in case of truncation
  873.  
  874.     or    prb_sys_flags[si],prbs_active
  875.                     ; Flag device as active
  876.  
  877.     call    FileOpen        ; Go open the file
  878.  
  879.     ret
  880.  
  881. SetRedir ENDP
  882.  
  883.  
  884. ;
  885. ;    Clear device redirection and close file.  If the file is active
  886. ;    (and not in an error state), its buffers are flushed.  The file
  887. ;    is then closed, regardless of its error state.  This routine
  888. ;    always returns zero in prb_status.
  889. ;
  890.  
  891. ClearRedir PROC    near
  892.  
  893.     test    prb_sys_flags[si],prbs_active
  894.                     ; Is redirection active?
  895.     jz    clear_redir_1
  896.     test    prb_status[si],0FFh    ; Skip flush on DOS error
  897.     jnz    clear_redir_1
  898.  
  899.     call    Flush            ; Flush the redirection buffer
  900.  
  901. clear_redir_1:
  902.     and    prb_sys_flags[si],not prbs_active
  903.                     ; Device no longer active
  904.  
  905.     test    prb_sys_flags[si],prbs_open
  906.                     ; Is the file open?
  907.     jz    clear_redir_2        ; Not open - can't close
  908.  
  909.     mov    bx,prb_handle[si]    ; BX = file handle
  910.     mov    ah,3Eh            ; DOS service:
  911.     int    21h            ;   Close file
  912.     mov    crit_flag,0h        ; Ignore ALL errors
  913.  
  914.     and    prb_sys_flags[si],not prbs_open
  915.                     ; File no longer open
  916.  
  917. clear_redir_2:
  918.     mov    prb_status[si],0    ; Clear channel status (just in case)
  919.     mov    prb_buf_count[si],0    ; Make sure buffer is empty
  920.     ret
  921.  
  922. ClearRedir ENDP
  923.  
  924.  
  925. ;
  926. ;    Flush buffer to disk.  This does the ol' standard trick of
  927. ;    duplicating the file handle then closing the duplicate.  Note
  928. ;    that an error trying to duplicate the handle does not cause
  929. ;    an error status for the channel.
  930. ;
  931.  
  932. FlushRedir PROC    near
  933.  
  934.     test    prb_sys_flags[si],prbs_active
  935.                     ; Redirection active?
  936.     jz    flush_redir_exit    ; No - can't flush anything
  937.     test    prb_status[si],0FFh    ; Redirection stopped by error?
  938.     jnz    flush_redir_exit    ; Yes - can't flush
  939.  
  940.     call    Flush            ; Flush redirection buffer
  941.  
  942.     test    prb_status[si],0FFh    ; Error while flushing buffer?
  943.     jnz    flush_redir_exit    ; Yes - quit while we're ahead
  944.  
  945.     mov    bx,prb_handle[si]    ; BX = handle to duplicate
  946.     mov    ah,45h            ; DOS service:
  947.     int    21h            ;   Duplicate file handle
  948.     jc    flush_redir_exit    ; Error - just leave quietly
  949.  
  950.     mov    bx,ax            ; BX = new handle
  951.     mov    ah,3Eh            ; DOS service:
  952.     int    21h            ;   Close file
  953.     jc    flush_redir_error
  954.     test    crit_flag,0FFh        ; Check for critical errors
  955.     jnz    flush_redir_crit
  956.     jmp    flush_redir_exit
  957.  
  958. flush_redir_error:
  959.     test    crit_flag,0FFh
  960.     jnz    flush_redir_1
  961. flush_redir_crit:
  962.     mov    al,83            ; Fake DOS 3.xx critical error code
  963. flush_redir_1:
  964.     mov    prb_status[si],al
  965. flush_redir_exit:
  966.     ret
  967.  
  968. FlushRedir ENDP
  969.  
  970.  
  971. ;
  972. ;    Reserved stack for interrupt handlers
  973. ;
  974.  
  975.         db    1EEh dup (?)    ; Total of 200h bytes stack
  976.  
  977. stack_es    dw    ?
  978. stack_ds    dw    ?
  979. stack_di    dw    ?
  980. stack_si    dw    ?
  981. stack_bp    dw    ?
  982. stack_dx    dw    ?
  983. stack_cx    dw    ?
  984. stack_bx    dw    ?
  985. stack_ax    label    word
  986. stack_al    db    ?
  987. stack_ah    db    ?
  988.  
  989. stack_top    equ    $        ; Initial value of SP
  990.  
  991.  
  992. ;
  993. ;    Device redirection buffers.  The actual space for the buffers
  994. ;    overlays the transient part of the program.  Printer output
  995. ;    (say, via PrtSc) between the time the INT 17h service 81h
  996. ;    call occurs and the time the main program TSRs could cause
  997. ;    some pretty interesting problems.
  998. ;
  999.  
  1000. lpt1_buf    equ    $
  1001. lpt2_buf    equ    lpt1_buf+buffer_size
  1002. lpt3_buf    equ    lpt2_buf+buffer_size
  1003. com1_buf    equ    lpt3_buf+buffer_size
  1004. com2_buf    equ    com1_buf+buffer_size
  1005.  
  1006. resident_size    equ    (com2_buf+buffer_size)-Code
  1007.                     ; Total size of resident section
  1008.  
  1009.     page
  1010.  
  1011. ;
  1012. ;    Transient data section
  1013. ;
  1014.  
  1015. switch_char    db    ?        ; Command line switch character
  1016. device_number    dw    ?        ; LPT1, 2, 3 = 0, 1, 2; COM1, 2 = 4, 5
  1017. user_filespec    db    MAXSPEC dup (?)    ; User-input filespec
  1018. dos_filespec    db    MAXSPEC dup (?)    ; DOS's translated filespec
  1019.  
  1020. switch_byte    db    0        ; Command line switches:
  1021.   sw_a          equ      80h        ;   /A - append
  1022.   sw_c          equ      40h        ;   /C - close
  1023.   sw_f          equ      20h        ;   /F - flush
  1024.   sw_n          equ      10h        ;   /N - not transparent
  1025.   sw_t          equ       8h        ;   /T - transparent
  1026. switch_names    db    "ACFNT   "    ; Switch characters (8 bytes)
  1027.  
  1028. leave_resident    db    0        ; Non-zero to leave via TSR
  1029.  
  1030. prb_ptr        dw    ?,?        ; Pointer to device redirection block
  1031.  
  1032.  
  1033. device_names    label    byte
  1034.         db    "LPT1",0    ; The first five names consist of
  1035.         db    "LPT2",0    ;   five bytes apiece, and are used
  1036.         db    "LPT3",0    ;   by DevStat as well as GetDevice
  1037.         db    "COM1",0
  1038.         db    "COM2",0
  1039.         db    "PRN",0
  1040.         db    "AUX",0
  1041.         db    0
  1042. device_ids    db    0,1,2,3,4,0,3    ; Translates PRN -> LPT1, AUX -> COM1
  1043.  
  1044.  
  1045. bad_dos_msg    db    "Incompatible DOS version",0Dh,0Ah,0
  1046. bad_dev_msg    db    "Unknown device name",0Dh,0Ah,0
  1047. bad_switch_msg    db    "Unknown or illegal switch",0Dh,0Ah,0
  1048. bad_cmd_msg    db    "Unrecognized command",0Dh,0Ah,0
  1049. not_there_msg    db    "Redirection software not installed",0Dh,0Ah,0
  1050. general_msg    db    "General-purpose error message",0Dh,0Ah,0
  1051. five_spaces    db    "     ",0
  1052.  
  1053. gen_err_msg    db    "DOS error code 0x",0
  1054. dos_messages    label    byte
  1055.         db    "File not found",0Dh,0Ah,0        ;  2
  1056.         db    "Path not found",0Dh,0Ah,0        ;  3
  1057.         db    "Too many open files",0Dh,0Ah,0        ;  4
  1058.         db    "Access denied",0Dh,0Ah,0        ;  5
  1059.         db    "Invalid handle",0Dh,0Ah,0        ;  6
  1060.         db    "Critical I/O error",0Dh,0Ah,0        ; 83
  1061. dos_codes    db    2,3,4,5,6,83    ; DOS codes for the error messages
  1062.                     ;   above
  1063.  
  1064.     page
  1065.  
  1066. ;
  1067. ;    COM program entry point
  1068. ;
  1069.  
  1070. PrnDsk    PROC    near
  1071.  
  1072.     mov    ah,30h            ; DOS service:
  1073.     int    21h            ;   Get DOS version number
  1074.     mov    dos_version,al        ; Save version number
  1075.     test    al,al            ; DOS 1.x?
  1076.     jnz    prndsk_1        ; No - breathe a sigh of relief
  1077.  
  1078.     mov    bx,offset bad_dos_msg    ; "Incompatible DOS version"
  1079.     call    PutStr
  1080.     mov    ah,0            ; DOS service:
  1081.     int    21h            ;   Exit program (DOS 1.x style)
  1082.  
  1083. prndsk_1:
  1084.     mov    ax,3700h        ; DOS service:
  1085.     int    21h            ;   Get switch character
  1086.     mov    switch_char,dl
  1087.  
  1088. ;    Get initial switches and device name from command line.
  1089.  
  1090.     mov    si,offset command_line
  1091.     call    SkipBl
  1092.     call    GetSwitch
  1093.     jc    bad_switch
  1094.  
  1095.     call    GetDevice
  1096.     jnc    prndsk_device        ; Branch if device name found
  1097.  
  1098. ;    No device name found.  Next character had better be a CR.
  1099.  
  1100.     test    switch_byte,not (sw_c+sw_f)
  1101.                     ; Only /C and /F allowed here
  1102.     jnz    bad_switch
  1103.     call    SkipBl
  1104.     cmp    al,0Dh            ; End of line?
  1105.     jnz    bad_command        ; No - something's wrong
  1106.  
  1107.     call    IsThere            ; Check that PRNDSK is installed
  1108.     jnz    not_there
  1109.  
  1110.     call    FormOne            ; Go handle the command
  1111.     jmp    prndsk_done
  1112.  
  1113. ;    Device name found.  Look for more switches and "="
  1114.  
  1115. prndsk_device:
  1116.     mov    device_number,dx    ; Save the device number
  1117.     call    SkipBl
  1118.     call    GetSwitch
  1119.     jc    bad_switch
  1120.     call    SkipBl
  1121.     cmp    al,'='
  1122.     jz    prndsk_spec        ; Branch if "=" found
  1123.  
  1124.     test    switch_byte,not (sw_c+sw_f+sw_n+sw_t)
  1125.                     ; Allowed here: /C/F/N/T
  1126.     jnz    bad_switch
  1127.     cmp    al,0Dh            ; End of line better be next
  1128.     jnz    bad_command        ; No - something's wrong
  1129.  
  1130.     call    IsThere            ; Can't do if PRNDSK not installed
  1131.     jnz    not_there
  1132.  
  1133.     call    FormTwo            ; Go handle the command
  1134.     jmp    prndsk_done
  1135.  
  1136. ;    "=" found after device name.  Get file specification and
  1137. ;    any switches following it.
  1138.  
  1139. prndsk_spec:
  1140.     inc    si            ; Skip the "="
  1141.     call    SkipBl
  1142.     call    GetSpec
  1143.     call    GetSwitch
  1144.     jc    bad_switch
  1145.  
  1146.     test    switch_byte,not (sw_a+sw_t)
  1147.                     ; Only /A and /T allowed
  1148.     jnz    bad_switch
  1149.     call    SkipBl
  1150.     cmp    al,0Dh            ; Followed by end of line?
  1151.     jnz    bad_command
  1152.  
  1153.     call    FormThree        ; Handle the command
  1154.     jmp    prndsk_done
  1155.  
  1156. bad_switch:                ; Unknown or illegal switch seen
  1157.     mov    bx,offset bad_switch_msg
  1158.     jmp    error_message
  1159. bad_command:                ; Bad device name or other error
  1160.     mov    bx,offset bad_cmd_msg
  1161.     jmp    error_message
  1162. not_there:                ; PRNDSK wasn't found in memory
  1163.     mov    bx,offset not_there_msg
  1164.     jmp    error_message
  1165.  
  1166. error_message:
  1167.     call    PutStr
  1168.  
  1169. prndsk_done:
  1170.     test    leave_resident,0FFh    ; Have the vectors been installed?
  1171.     jnz    prndsk_tsr        ; Yes - leave PRNDSK in memory
  1172. prndsk_exit:
  1173.     mov    ax,4C00h        ; DOS service:
  1174.     int    21h            ;   Exit with return code = 0
  1175. prndsk_tsr:
  1176.     mov    es,env_seg_ptr        ; ES points to PRNDSK's environment
  1177.     mov    ah,49h            ; DOS service:
  1178.     int    21h            ;   Free allocated memory
  1179.     mov    dx,(resident_size+15)/16
  1180.                     ; DX = resident length (paragraphs)
  1181.     mov    ax,3100h        ; DOS service:
  1182.     int    21h            ;   Terminate and Keep Resident
  1183.  
  1184. PrnDsk    ENDP
  1185.  
  1186.  
  1187. ;
  1188. ;    Handle commands of the form PRNDSK[/switches]
  1189. ;
  1190.  
  1191. FormOne    PROC    near
  1192.  
  1193.     test    byte ptr switch_byte,0FFh
  1194.     jz    list_all
  1195.     test    byte ptr switch_byte,sw_c
  1196.     jnz    close_all
  1197.     test    byte ptr switch_byte,sw_f
  1198.     jnz    flush_all
  1199.  
  1200.     ret
  1201.  
  1202. list_all:                ; PRNDSK <cr> - device status list
  1203.     mov    dx,0            ; Start with device 0 (PRN)
  1204. list_loop:
  1205.     push    dx
  1206.     call    DevStat            ; Give status of device
  1207.     pop    dx
  1208.     inc    dx            ; Next device
  1209.     cmp    dx,5
  1210.     jb    list_loop
  1211.     ret
  1212.  
  1213. close_all:                ; PRNDSK/C - close all devices
  1214.     mov    dx,0
  1215. close_loop:
  1216.     push    dx
  1217.     mov    ah,82h            ; Extended printer service:
  1218.     int    17h            ;   Close and terminate
  1219.     pop    dx
  1220.     inc    dx
  1221.     cmp    dx,5
  1222.     jb    close_loop
  1223.     ret
  1224.  
  1225. flush_all:                ; PRNDSK/F - flush all channels
  1226.     mov    dx,0
  1227. flush_loop:
  1228.     push    dx
  1229.     mov    ah,83h            ; Extended printer service:
  1230.     int    17h            ;   Flush to disk
  1231.     pop    dx
  1232.     inc    dx
  1233.     cmp    dx,5
  1234.     jb    flush_loop
  1235.     ret
  1236.  
  1237. FormOne    ENDP
  1238.  
  1239.  
  1240. ;
  1241. ;    Handle commands of the form PRNDSK dev[/switches]
  1242. ;
  1243.  
  1244. FormTwo    PROC    near
  1245.  
  1246.     test    byte ptr switch_byte,0FFh
  1247.     jz    list_dev
  1248.     test    byte ptr switch_byte,sw_c
  1249.     jnz    close_dev
  1250.     test    byte ptr switch_byte,sw_f
  1251.     jnz    flush_dev
  1252. form_2_1:
  1253.     test    byte ptr switch_byte,sw_t
  1254.     jnz    dev_transp
  1255.     test    byte ptr switch_byte,sw_n
  1256.     jnz    not_transp
  1257.  
  1258.     ret
  1259.  
  1260. list_dev:                ; PRNDSK device <cr>
  1261.     mov    dx,device_number
  1262.     call    DevStat
  1263.     ret
  1264.  
  1265. close_dev:                ; PRNDSK device/C
  1266.     mov    dx,device_number
  1267.     mov    ah,82h            ; Extended printer service:
  1268.     int    17h            ;   Close and terminate redirection
  1269.     test    al,al
  1270.     jnz    form_2_error
  1271.     ret
  1272.  
  1273. flush_dev:                ; PRNDSK device/F
  1274.     mov    dx,device_number
  1275.     mov    ah,83h            ; Extended printer service:
  1276.     int    17h            ;   Flush to disk
  1277.     test    al,al
  1278.     jnz    form_2_error
  1279.     jmp    form_2_1
  1280.  
  1281. dev_transp:                ; PRNDSK device/T
  1282.     mov    bl,prbu_transp
  1283. dev_transp_1:
  1284.     mov    dx,device_number
  1285.     mov    ah,86h            ; Extended printer service:
  1286.     int    17h            ;   Get user flag byte
  1287.     and    al,not prbu_transp
  1288.     or    al,bl
  1289.     mov    ah,85h            ; Extended printer service:
  1290.     int    17h            ;   Set user flag byte
  1291.     ret
  1292.  
  1293. not_transp:                ; PRNDSK device/N
  1294.     mov    bl,0h
  1295.     jmp    dev_transp_1
  1296.  
  1297. form_2_error:
  1298.     call    DOSError
  1299.     ret
  1300.  
  1301. FormTwo    ENDP
  1302.  
  1303.  
  1304. ;
  1305. ;    Handle a command of the form PRNDSK dev=filespec[/switches]
  1306. ;
  1307.  
  1308. FormThree PROC    near
  1309.  
  1310.     push    ds            ; Move DS...
  1311.     pop    es            ; ...to ES
  1312.     mov    si,offset user_filespec    ; DS:SI points to original filespec
  1313.     mov    di,offset dos_filespec    ; ES:DI points to translated filespec
  1314.     mov    cx,MAXSPEC
  1315.     cmp    dos_version,3        ; Service 60 doesn't exist
  1316.     jb    form_3_dos_2        ;   in DOS 2.xx
  1317.  
  1318.     mov    ah,60h            ; DOS service:
  1319.     int    21h            ;   Translate filespec
  1320.     jc    form_3_error
  1321.     jmp    form_3_install
  1322.  
  1323. form_3_dos_2:                ; Copy filespec, convert to upper case
  1324.     lodsb
  1325.     call    Fold
  1326.     stosb
  1327.     test    al,al
  1328.     loopnz    form_3_dos_2
  1329.  
  1330. form_3_install:
  1331.     call    Install            ; Make sure PRNDSK services installed
  1332.  
  1333. ;    Set the "append" and "transparent" bits in the user flag byte
  1334.  
  1335.     xor    al,al
  1336.     test    byte ptr switch_byte,sw_a
  1337.     jz    form_3_1
  1338.     or    al,prbu_append        ; /A - set append flag
  1339. form_3_1:
  1340.     test    byte ptr switch_byte,sw_t
  1341.     jz    form_3_2
  1342.     or    al,prbu_transp        ; /T - set transparent flag
  1343. form_3_2:
  1344.  
  1345.     mov    si,offset dos_filespec    ; DS:SI points to filespec
  1346.     mov    dx,device_number    ; DX = device number
  1347.     mov    ah,81h            ; Extended printer service:
  1348.     int    17h            ;   Start device redirection
  1349.     test    al,al
  1350.     jnz    form_3_error
  1351.  
  1352.     ret
  1353.  
  1354. form_3_error:
  1355.     call    DOSError
  1356.     ret
  1357.  
  1358. FormThree ENDP
  1359.  
  1360.     page
  1361.  
  1362. ;
  1363. ;    Check whether PRNDSK is already installed
  1364. ;
  1365. ;    Exit:    ZF = true if PRNDSK is there
  1366. ;
  1367.  
  1368. IsThere    PROC    near
  1369.  
  1370.     mov    ah,80h            ; Extended printer service:
  1371.     int    17h            ;   Return PRNDSK installation flag
  1372.     cmp    ax,5A5Ah
  1373.     jnz    is_there_exit
  1374.  
  1375. ;    Installation flag ok; check program IDs to make sure
  1376.  
  1377.     mov    si,offset program_id
  1378.     mov    di,si
  1379.     mov    cx,id_length
  1380.     repz    cmpsb
  1381.  
  1382. is_there_exit:
  1383.     ret
  1384.  
  1385. IsThere    ENDP
  1386.  
  1387.  
  1388. ;
  1389. ;    Make sure PRNDSK is installed - install it if necessary
  1390. ;
  1391.  
  1392. Install    PROC    near
  1393.  
  1394.     call    IsThere            ; Already installed?
  1395.     jz    install_exit        ; Yes - we're done
  1396.  
  1397.     mov    ah,51h            ; DOS service:
  1398.     int    21h            ;   Get Process ID
  1399.     mov    tsr_pid,bx        ; Save TSR's PID
  1400.  
  1401.     mov    ax,3521h        ; DOS service:
  1402.     int    21h            ;   Get interrupt vector 21h
  1403.     mov    old_int_21h+0,bx    ; Store old INT 21h vector
  1404.     mov    old_int_21h+2,es
  1405.  
  1406.     mov    dx,offset Int_21h    ; Point to our INT 21h handler
  1407.     mov    ax,2521h        ; DOS service:
  1408.     int    21h            ;   Set interrupt vector 21h
  1409.  
  1410.     mov    ax,3517h        ; DOS service:
  1411.     int    21h            ;   Get interrupt vector 17h
  1412.     mov    old_int_17h+0,bx    ; Store old printer services vector
  1413.     mov    old_int_17h+2,es
  1414.  
  1415.     mov    dx,offset Int_17h    ; Point to our INT 17h handler
  1416.     mov    ax,2517h        ; DOS service:
  1417.     int    21h            ;   Set interrupt vector 17h
  1418.  
  1419.     mov    ax,3514h        ; DOS service:
  1420.     int    21h            ;   Get interrupt vector 14h
  1421.     mov    old_int_14h+0,bx    ; Store old serial services vector
  1422.     mov    old_int_14h+2,es
  1423.  
  1424.     mov    dx,offset Int_14h    ; Point to our INT 14h handler
  1425.     mov    ax,2514h        ; DOS service:
  1426.     int    21h            ;   Set interrupt vector 14h
  1427.  
  1428.     mov    leave_resident,0FFh    ; Indicate handlers to be left resident
  1429.  
  1430. install_exit:
  1431.     ret
  1432.  
  1433. Install    ENDP
  1434.  
  1435.  
  1436. ;
  1437. ;    Skip blanks on command line
  1438. ;
  1439. ;    Exit:    AL = next non-blank character
  1440. ;
  1441.  
  1442. SkipBl    PROC    near
  1443. skipbl_loop:
  1444.     mov    al,[si]
  1445.     cmp    al,' '
  1446.     jnz    skipbl_exit
  1447.     inc    si
  1448.     jmp    skipbl_loop
  1449. skipbl_exit:
  1450.     ret
  1451. SkipBl    ENDP
  1452.  
  1453.  
  1454. ;
  1455. ;    Fold a lower-case character to upper-case
  1456. ;
  1457. ;    Entry:    AL = character
  1458. ;
  1459.  
  1460. Fold    PROC    near
  1461.     cmp    al,'a'
  1462.     jb    fold_exit
  1463.     cmp    al,'z'
  1464.     ja    fold_exit
  1465.     sub    al,'a'-'A'
  1466. fold_exit:
  1467.     ret
  1468. Fold    ENDP
  1469.  
  1470.  
  1471. ;
  1472. ;    Get a list of switches
  1473. ;
  1474. ;    Entry:    SI points to next character on command line
  1475. ;
  1476. ;    Exit:    Switch bits have been added to switch_byte
  1477. ;
  1478.  
  1479. GetSwitch PROC    near
  1480.  
  1481. next_switch:
  1482.     call    SkipBl
  1483.     cmp    al,switch_char        ; Another switch?
  1484.     jnz    get_switch_exit
  1485.  
  1486.     inc    si            ; Point to next character
  1487.     mov    al,[si]            ; Get switch identifier
  1488.     call    Fold
  1489.  
  1490.     mov    dh,80h            ; Bit 7 for first switch in list
  1491.     mov    bx,0            ; Index into switch name table
  1492. switch_loop:
  1493.     cmp    al,switch_names[bx]    ; Do the names match?
  1494.     jz    got_switch
  1495.     inc    bx            ; No - next switch name
  1496.     shr    dh,1            ;   and next switch bit
  1497.     jnc    switch_loop
  1498.     ret                ; With carry set
  1499.  
  1500. got_switch:
  1501.     or    switch_byte,dh        ; Set switch bit
  1502.     inc    si            ; Swallow character from command line
  1503.     jmp    next_switch
  1504.  
  1505. get_switch_exit:
  1506.     clc
  1507.     ret
  1508.  
  1509. GetSwitch ENDP
  1510.  
  1511.  
  1512. ;
  1513. ;    Get a device name from the command line
  1514. ;
  1515.  
  1516. GetDevice PROC    near
  1517.  
  1518.     mov    di,offset device_names    ; DI points into device name table
  1519.     xor    dx,dx            ; DX is entry number in table
  1520.  
  1521. get_dev_1:
  1522.     test    byte ptr [di],0FFh    ; Found end of table?
  1523.     jz    no_device
  1524.     xor    bx,bx            ; BX is offset into device name
  1525.  
  1526. get_dev_2:
  1527.     test    byte ptr [di+bx],0FFh    ; Found end of name in table?
  1528.     jz    got_device
  1529.  
  1530.     mov    al,[si+bx]        ; Get next character from command line
  1531.     call    Fold            ; Convert to upper case
  1532.     cmp    al,[di+bx]        ; Does it match the device name?
  1533.     jnz    skip_device        ; No - skip this name
  1534.  
  1535.     inc    bx            ; Advance to next character
  1536.     jmp    get_dev_2
  1537.  
  1538. skip_device:                ; This device doesn't match - get next
  1539.     mov    al,[di]
  1540.     inc    di            ; Advance device name table pointer
  1541.     test    al,al            ;   until a zero is seen
  1542.     jnz    skip_device
  1543.     inc    dx            ; Increment device number
  1544.     jmp    get_dev_1
  1545.  
  1546. no_device:                ; No match for name - return error
  1547.     stc
  1548.     ret
  1549.  
  1550. got_device:
  1551.     add    si,bx            ; Advance command line pointer
  1552.     cmp    byte ptr [si],':'    ; Check for ":" after name
  1553.     jnz    got_dev_1
  1554.     inc    si            ; Eat ":"
  1555.  
  1556. got_dev_1:
  1557.     mov    bx,dx
  1558.     mov    dl,device_ids[bx]    ; This maps PRN to LPT1, AUX to COM1
  1559.     clc
  1560.     ret
  1561.  
  1562. GetDevice ENDP
  1563.  
  1564.  
  1565. ;
  1566. ;    Get a filespec from the command line.  Characters are copied
  1567. ;    until finding a blank, end of line, or the switch character.
  1568. ;
  1569. ;    Entry:    SI points to command line (current position)
  1570. ;
  1571. ;    Exit:    SI points to first character after filespec
  1572. ;
  1573.  
  1574. GetSpec    PROC    near
  1575.  
  1576.     mov    di,offset user_filespec    ; Destination pointer
  1577.     mov    cx,MAXSPEC-1        ; Maximum characters to copy
  1578.  
  1579. spec_loop:
  1580.     mov    al,[si]            ; Get next character
  1581.     cmp    al,0Dh            ; Check for EOL...
  1582.     jz    got_spec
  1583.     cmp    al,' '            ; ...or blank...
  1584.     jz    got_spec
  1585.     cmp    al,switch_char        ; ...or switch character
  1586.     jz    got_spec
  1587.  
  1588.     mov    [di],al            ; Store character in buffer
  1589.     inc    si            ; Advance pointers
  1590.     inc    di
  1591.     loop    spec_loop
  1592.  
  1593. got_spec:
  1594.     mov    byte ptr [di],0        ; Just in case CX ran out
  1595.     ret
  1596.  
  1597. GetSpec    ENDP
  1598.  
  1599.  
  1600. ;
  1601. ;    Print the status of a redirectable device.  The device name
  1602. ;    and filespec are printed, along with the transparency flag
  1603. ;    and (if appropriate) error message.
  1604. ;
  1605. ;    Entry:    DX = device number (0 to 4)
  1606. ;
  1607.  
  1608. DevStat    PROC    near
  1609.  
  1610.     mov    ah,84h            ; Extended printer service:
  1611.     int    17h            ;   Get PRB pointer
  1612.     test    es:prb_sys_flags[bx],prbs_active
  1613.                     ; Redirection active for this device?
  1614.     jz    dev_stat_2
  1615.  
  1616.     mov    prb_ptr+0,bx        ; Save PRB pointer
  1617.     mov    prb_ptr+2,es
  1618.  
  1619.     mov    bx,dx            ; BX = DX * 5 (offset into device
  1620.     shl    bx,1            ;   names table)
  1621.     shl    bx,1
  1622.     add    bx,dx
  1623.     add    bx,offset device_names    ; BX points to device name
  1624.     call    PutStr
  1625.  
  1626.     mov    dl,'='
  1627.     call    PutChar
  1628.  
  1629.     push    ds
  1630.     lds    bx,dword ptr cs:prb_ptr    ; DS:BX points to PRB...
  1631.     add    bx,prb_filespec        ; ...now points to filespec
  1632.     call    PutStr
  1633.     pop    ds
  1634.  
  1635.     les    bx,dword ptr cs:prb_ptr
  1636.     test    es:prb_user_flags[bx],prbu_transp
  1637.                     ; Check transparency flag
  1638.     jz    dev_stat_1
  1639.  
  1640.     mov    dl,switch_char        ; Print a "/" (or something like it)
  1641.     call    PutChar
  1642.     mov    dl,'T'            ; Print a "T"
  1643.     call    PutChar
  1644.  
  1645. dev_stat_1:
  1646.     mov    dl,0Dh
  1647.     call    PutChar
  1648.     mov    dl,0Ah
  1649.     call    PutChar
  1650.  
  1651.     mov    al,es:prb_status[bx]    ; Get device status byte
  1652.     test    al,al
  1653.     jz    dev_stat_2        ; Branch if no error
  1654.  
  1655.     push    ax
  1656.     mov    bx,offset five_spaces
  1657.     call    PutStr
  1658.  
  1659.     pop    ax
  1660.     call    DOSError
  1661.  
  1662. dev_stat_2:
  1663.     ret
  1664.  
  1665. DevStat    ENDP
  1666.  
  1667.  
  1668. ;
  1669. ;    Print a DOS error message
  1670. ;
  1671. ;    Entry:    AL = error code
  1672. ;
  1673.  
  1674. DOSError PROC    near
  1675.  
  1676. ;    First, search the dos_codes table to see if there is a
  1677. ;    specific error message for this code.
  1678.  
  1679.     xor    bx,bx
  1680. find_message:
  1681.     test    dos_codes[bx],0FFh    ; End of table?
  1682.     jz    no_message
  1683.     cmp    al,dos_codes[bx]    ; Found a match for error code?
  1684.     jz    got_msg_number
  1685.     inc    bx            ; Next table entry
  1686.     jmp    find_message
  1687.  
  1688. ;    No special message, use the general-purpose one.
  1689.  
  1690. no_message:
  1691.     push    ax
  1692.     mov    bx,offset gen_err_msg    ; "DOS error code 0x"
  1693.     call    PutStr
  1694.     pop    ax
  1695.     call    PutHex            ; Print error number
  1696.     mov    dl,0Dh            ; Print <CR>...
  1697.     call    PutChar
  1698.     mov    dl,0Ah            ; ...<LF>
  1699.     call    PutChar
  1700.     ret
  1701.  
  1702. ;    BX contains the message number.  Scan through the message
  1703. ;    table to find the string represented by that number.
  1704.  
  1705. got_msg_number:
  1706.     mov    cx,bx            ; CX counts down from message number
  1707.     mov    bx,offset dos_messages    ; Point to start of messages
  1708.     jcxz    got_msg_ptr        ; Branch for message 0
  1709. find_msg_ptr:
  1710.     mov    al,[bx]            ; Get message character
  1711.     inc    bx            ;   and advance pointer
  1712.     test    al,al            ; Found end of a string?
  1713.     jnz    find_msg_ptr        ; No - continue scanning
  1714.     loop    find_msg_ptr        ; Loop till correct number passed
  1715.  
  1716. got_msg_ptr:
  1717.     call    PutStr
  1718.     ret
  1719.  
  1720. DOSError ENDP
  1721.  
  1722.  
  1723. ;
  1724. ;    Print a zero-terminated string
  1725. ;
  1726. ;    Entry:    BX points to string
  1727. ;
  1728.  
  1729. PutStr    PROC    near
  1730.  
  1731. put_str_loop:
  1732.     mov    dl,[bx]
  1733.     test    dl,dl
  1734.     jz    put_str_exit
  1735.     call    PutChar
  1736.     inc    bx
  1737.     jmp    put_str_loop
  1738.  
  1739. put_str_exit:
  1740.     ret
  1741.  
  1742. PutStr    ENDP
  1743.  
  1744.  
  1745. ;
  1746. ;    Print a character
  1747. ;
  1748. ;    Entry:    DL = character
  1749. ;
  1750.  
  1751. PutChar    PROC    near
  1752.     mov    ah,2h
  1753.     int    21h
  1754.     ret
  1755. PutChar    ENDP
  1756.  
  1757.  
  1758. ;
  1759. ;    Print two hex digits
  1760. ;
  1761. ;    Entry:    AL = hex byte to print
  1762. ;
  1763.  
  1764. PutHex    PROC    near
  1765.  
  1766.     push    ax
  1767.     shr    al,1
  1768.     shr    al,1
  1769.     shr    al,1
  1770.     shr    al,1
  1771.     call    put_hex_digit
  1772.     pop    ax
  1773.     and    al,0Fh
  1774.  
  1775. put_hex_digit:
  1776.     add    al,90h
  1777.     daa
  1778.     adc    al,40h
  1779.     daa
  1780.     mov    dl,al
  1781.     call    PutChar
  1782.     ret
  1783.  
  1784. PutHex    ENDP
  1785.  
  1786.  
  1787. Code    ENDS
  1788.     END    Start
  1789.